package com.md.util;

import com.google.common.collect.Lists;
import com.md.model.html.LabelStyle;
import com.md.model.word.Document;
import com.md.value.html.ColorIndex;
import com.md.value.html.FontStyle;
import com.md.value.html.FontWeight;
import com.md.value.html.LabelStyleType;
import com.md.value.html.TextAlign;
import com.md.value.html.TextDecoration;
import com.md.value.html.Unit;
import lombok.experimental.UtilityClass;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.UnderlinePatterns;
import org.apache.poi.xwpf.usermodel.XWPFAbstractNum;
import org.apache.poi.xwpf.usermodel.XWPFNum;
import org.apache.xmlbeans.SimpleValue;
import org.apache.xmlbeans.StringEnumAbstractBase;
import org.apache.xmlbeans.XmlAnySimpleType;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlTokenSource;
import org.openxmlformats.schemas.drawingml.x2006.main.CTBaseStyles;
import org.openxmlformats.schemas.drawingml.x2006.main.CTFontCollection;
import org.openxmlformats.schemas.drawingml.x2006.main.CTOfficeStyleSheet;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextFont;
import org.openxmlformats.schemas.drawingml.x2006.main.ThemeDocument;
import org.openxmlformats.schemas.officeDocument.x2006.sharedTypes.STOnOff;
import org.openxmlformats.schemas.officeDocument.x2006.sharedTypes.STOnOff1;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTAbstractNum;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFldChar;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHpsMeasure;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTInd;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTJc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTNum;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTNumPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTOnOff;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPrBase;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSpacing;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTString;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTStyle;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTblWidth;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTUnderline;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STHexColor;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STMeasurementOrPercent;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTblWidth;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STTheme;

import java.util.List;
import java.util.Optional;
import java.util.function.Function;

/**
 * @author 刘骄阳
 * 2024-06-18 11:05
 */
@UtilityClass
public class POIUtil {
  /**
   * @param tokenSource xmlObject
   * @param path        路径
   * @return 符合条件的集合
   */
  public static List<XmlObject> parseXmlObject2List(final XmlTokenSource tokenSource, final String path) {

    List<XmlObject> list = Lists.newArrayList();
    XmlCursor newCursor = tokenSource.newCursor();
    newCursor.selectPath(path);
    while (newCursor.toNextSelection()) {
      list.add(newCursor.getObject());
    }
    newCursor.dispose();
    return list;
  }

  public static Float getFloatValue(final XmlAnySimpleType value) {
    return ((SimpleValue) value).getFloatValue();
  }

  public static boolean isON(final CTOnOff ctOnOff) {
    Optional<CTOnOff> optionalCTOnOff = Optional.ofNullable(ctOnOff);
    return optionalCTOnOff.map(CTOnOff::xgetVal).map(POIUtil::isON).orElse(optionalCTOnOff.map(CTOnOff::isSetVal)
      .map(isOn -> !isOn).orElse(false));
  }

  private static boolean isON(final CTOnOff[] ctOnOffs) {
    return Optional.ofNullable(ctOnOffs).filter(ObjectUtil::notEmpty).map(array ->
      isON(array[0])).orElse(false);
  }

  public static <T extends XmlObject> Optional<T> getFirst(final T[] array) {
    if (ObjectUtil.notEmpty(array)) {
      return Optional.of(array[0]);
    }
    return Optional.empty();
  }


  public static boolean isON(final STOnOff st) {
    return Optional.ofNullable(st).map(STOnOff::getObjectValue).map(stValue -> {
      if (stValue instanceof Boolean) {
        return (Boolean) stValue;
      } else {
        return STOnOff1.ON == ((STOnOff1) stValue).getEnumValue();
      }
    }).orElse(false);
  }

  public static String getColor(final STHexColor stHexColor, final String defaultColor) {
    if (stHexColor == null) {
      return defaultColor;
    }
    String colorHex = stHexColor.getStringValue();
    if ("auto".equals(colorHex)) {
      return defaultColor;
    } else {
      // ??? 为什么要转一遍类型
      return "#" + stHexColor.getStringValue();
    }
  }


  public static Optional<LabelStyle> getWeight(final CTTblWidth tblW) {
    return Optional.ofNullable(tblW).map(ctTblWidth -> {
      STMeasurementOrPercent stMeasurementOrPercent = ctTblWidth.xgetW();
      // todo 可能会抛出异常, float 转换
      Float floatValue = POIUtil.getFloatValue(stMeasurementOrPercent);
      if (STTblWidth.INT_PCT == ctTblWidth.getType().intValue()) {
        return LabelStyle.of(LabelStyleType.WIDTH, floatValue, Unit.PCT);
      } else {
        return LabelStyle.of(LabelStyleType.WIDTH, floatValue, Unit.DXA);
      }
    });
  }

  public static Optional<CTNumPr> getNumber(final CTNumPr ctNum, final Document documentStyle) {
    return getXWPFNum(ctNum, documentStyle).flatMap(xwpfNum ->
      getXWPFAbstractNum(xwpfNum, documentStyle).map(XWPFAbstractNum::getAbstractNum)
        .map(CTAbstractNum::getNumStyleLink).map(CTString::getVal).map(styleId ->
          documentStyle.getStyleMap().get(styleId)).map(CTStyle::getPPr).flatMap(pPr ->
          getNumber(pPr.getNumPr(), documentStyle)));
  }

  public static Optional<XWPFNum> getXWPFNum(final CTNumPr numPr, final Document documentStyle) {
    // numID can be null, ignore the numbering
    // see https://code.google.com/p/xdocreport/issues/detail?id=239
    return Optional.ofNullable(numPr).map(CTNumPr::getNumId).map(numId ->
      documentStyle.getXwpfDocument().getNumbering().getNum(numId.getVal()));
  }

  public static Optional<XWPFAbstractNum> getXWPFAbstractNum(final XWPFNum num, final Document documentStyle) {
    return Optional.ofNullable(num).map(XWPFNum::getCTNum).map(CTNum::getAbstractNumId).map(abstractNumID ->
      documentStyle.getXwpfDocument().getNumbering().getAbstractNum(abstractNumID.getVal()));
  }


  public static Optional<LabelStyle> getMarginLeft(final CTPPrBase ctpPrBase) {
    return Optional.ofNullable(ctpPrBase).map(CTPPrBase::getInd).map(CTInd::xgetLeft).map(POIUtil::getFloatValue)
      .map(size -> LabelStyle.of(LabelStyleType.MARGIN_LEFT, Unit.DXA.convert(Unit.PX, size.doubleValue()), Unit.PX));
  }

  public static Optional<LabelStyle> getMarginRight(final CTPPrBase ctpPrBase) {
    return Optional.ofNullable(ctpPrBase).map(CTPPrBase::getInd).map(CTInd::xgetRight).map(POIUtil::getFloatValue)
      .map(size -> LabelStyle.of(LabelStyleType.MARGIN_RIGHT, Unit.DXA.convert(Unit.PX,
        size.doubleValue()), Unit.PX));
  }

  public static Optional<LabelStyle> getMarginTop(final CTPPrBase ctpPrBase) {
    return Optional.ofNullable(ctpPrBase).map(CTPPrBase::getSpacing).map(CTSpacing::xgetBefore)
      .map(POIUtil::getFloatValue).map(size -> LabelStyle.of(LabelStyleType.MARGIN_TOP,
        Unit.DXA.convert(Unit.PX, size.doubleValue()), Unit.PX));
  }

  public static Optional<LabelStyle> getMarginBottom(final CTPPrBase ctpPrBase) {
    return Optional.ofNullable(ctpPrBase).map(CTPPrBase::getSpacing).map(CTSpacing::xgetAfter)
      .map(POIUtil::getFloatValue).map(size -> LabelStyle.of(LabelStyleType.MARGIN_BOTTOM,
        Unit.DXA.convert(Unit.PX, size.doubleValue()), Unit.PX));
  }

  public static Optional<LabelStyle> getFirstLineIndent(final CTPPrBase ctpPrBase) {
    return Optional.ofNullable(ctpPrBase).map(CTPPrBase::getInd).map(CTInd::xgetFirstLine).map(POIUtil::getFloatValue)
      .map(size -> LabelStyle.of(LabelStyleType.TEXT_INDENT, Unit.DXA.convert(Unit.PX, size.doubleValue()), Unit.PX));
  }


  public static Optional<Float> getMargin(final CTPPrBase ctpPrBase,
                                          final Function<CTInd, XmlAnySimpleType> marginExtractor) {
    return Optional.ofNullable(ctpPrBase).map(CTPPrBase::getInd).map(marginExtractor).map(POIUtil::getFloatValue);
  }

  public static Optional<Float> getSpacing(final CTPPrBase ctpPrBase,
                                           final Function<CTSpacing, XmlAnySimpleType> position) {
    return Optional.ofNullable(ctpPrBase).map(CTPPrBase::getSpacing).map(position).map(POIUtil::getFloatValue);
  }

  public static Optional<LabelStyle> getAlignment(final CTPPrBase ctpPrBase) {
    return Optional.ofNullable(ctpPrBase).map(CTPPrBase::getJc).map(CTJc::getVal).map(StringEnumAbstractBase::intValue)
      .map(ParagraphAlignment::valueOf).map(TextAlign::convertToHtml).map(TextAlign::getValue).map(textAlign ->
        LabelStyle.of(LabelStyleType.TEXT_ALIGN, textAlign));
  }

  public static Optional<LabelStyle> getTextDecoration(final CTRPr rPr) {
    List<String> textDecoration = Lists.newArrayList();
    // 删除线
    getFirst(rPr.getStrikeArray()).filter(POIUtil::isON).ifPresent(xmlObject ->
      textDecoration.add(TextDecoration.LINE_THROUGH.getCode()));
    // 下划线
    getFirst(rPr.getUArray()).map(CTUnderline::getVal).filter(underline ->
        UnderlinePatterns.valueOf(underline.intValue()) == UnderlinePatterns.SINGLE)
      .ifPresent(underlinePatterns -> textDecoration.add(TextDecoration.UNDERLINE.getCode()));

    if (textDecoration.isEmpty()) {
      return Optional.empty();
    }
    return Optional.of(LabelStyle.of(LabelStyleType.TEXT_DECORATION, String.join(" ", textDecoration)));
  }

  public static Optional<LabelStyle> getBlob(final CTRPr rPr) {
    return getFirst(rPr.getBArray()).map(isBlob -> {
      FontWeight weight = FontWeight.NORMAL;
      if (POIUtil.isON(isBlob)) {
        weight = FontWeight.BOLD;
      }
      return LabelStyle.of(LabelStyleType.FONT_WEIGHT, weight);
    });
  }

  public static Optional<LabelStyle> getItalic(final CTRPr rPr) {
    return getFirst(rPr.getIArray()).map(isItalic -> {
      FontStyle italic = FontStyle.NORMAL;
      if (POIUtil.isON(isItalic)) {
        italic = FontStyle.ITALIC;
      }
      return LabelStyle.of(LabelStyleType.FONT_STYLE, italic);
    });
  }

  public static Optional<LabelStyle> getColor(final CTRPr rPr) {
    return getFirst(rPr.getColorArray()).map(ctColor ->
      LabelStyle.of(LabelStyleType.COLOR, getColor(ctColor.xgetVal(), ColorIndex.BLACK.getHex())));
  }

  public static Optional<LabelStyle> getFontSize(final CTRPr rPr) {
    return Optional.ofNullable(rPr.getSzArray()).filter(ObjectUtil::notEmpty).map(ctHpsMeasures -> ctHpsMeasures[0])
      .map(CTHpsMeasure::xgetVal).map(POIUtil::getFloatValue).map(size ->
        LabelStyle.of(LabelStyleType.FONT_SIZE, size, Unit.PT));
  }

  /**
   * 获取字体家族
   *
   * @param rPr rPr
   * @return 字体家族
   */
  public static Optional<LabelStyle> getFontFamily(final CTRPr rPr, final List<ThemeDocument> themeDocuments) {
    return getFirst(rPr.getRFontsArray()).map(ctFont -> {
      STTheme.Enum asciiTheme = ctFont.getAsciiTheme();
      String fontFamilyValue;
      if (asciiTheme == null) {
        fontFamilyValue = ctFont.getAscii();
      } else {
        fontFamilyValue = themeDocuments.stream()
          .map(temp -> getFontCollection(temp, asciiTheme))
          .filter(Optional::isPresent)
          .map(Optional::get)
          .map(CTFontCollection::getLatin)
          .map(CTTextFont::getTypeface)
          .findFirst().orElse(null);
      }
      if (ObjectUtil.notEmpty(fontFamilyValue)) {
        return LabelStyle.of(LabelStyleType.FONT_FAMILY, fontFamilyValue);
      }
      return null;
    });
  }

  private static Optional<CTFontCollection> getFontCollection(final ThemeDocument themeDocument,
                                                              final STTheme.Enum asciiTheme) {
    return Optional.ofNullable(themeDocument.getTheme()).map(CTOfficeStyleSheet::getThemeElements)
      .map(CTBaseStyles::getFontScheme).map(fontScheme -> {
        if (asciiTheme.equals(STTheme.MINOR_ASCII) || asciiTheme.equals(STTheme.MINOR_BIDI)
          || asciiTheme.equals(STTheme.MINOR_EAST_ASIA) || asciiTheme.equals(STTheme.MINOR_H_ANSI)) {
          return fontScheme.getMinorFont();
        }
        return fontScheme.getMajorFont();
      });
  }

  /**
   * 获取高亮
   *
   * @param rPr rpr
   * @return 背景色样式
   */
  public static Optional<LabelStyle> getShd(final CTRPr rPr) {
    return getFirst(rPr.getShdArray()).map(ctShd ->
      LabelStyle.of(LabelStyleType.BACKGROUND_COLOR, POIUtil.getColor(ctShd.xgetFill(), ColorIndex.BLACK.getHex())));
  }

  public static Optional<LabelStyle> getHighlight(final CTRPr rPr) {
    return getFirst(rPr.getHighlightArray()).map(highlight ->
      LabelStyle.of(LabelStyleType.BACKGROUND_COLOR,
        ColorIndex.of(highlight.xgetVal().getStringValue()).getHex()));
  }

  public static Optional<CTFldChar> getCTFldChar(final CTR ctr) {
    return getFirst(ctr.getFldCharArray());
  }

  public static List<LabelStyle> getFontStyle(final CTRPr rPr, final List<ThemeDocument> themeDocuments) {
    if (ObjectUtil.isEmpty(rPr)) {
      return Lists.newArrayList();
    }
    List<LabelStyle> labelStyles = Lists.newArrayList();
    // 字体风格
    POIUtil.getFontFamily(rPr, themeDocuments).ifPresent(labelStyles::add);
    // 字体大小
    POIUtil.getFontSize(rPr).ifPresent(labelStyles::add);
    // 加粗
    getBlob(rPr).ifPresent(labelStyles::add);
    // 倾斜
    getItalic(rPr).ifPresent(labelStyles::add);
    // 字体颜色
    POIUtil.getColor(rPr).ifPresent(labelStyles::add);
    // 下划线、删除线
    POIUtil.getTextDecoration(rPr).ifPresent(labelStyles::add);
    // 背景色
    getShd(rPr).ifPresent(labelStyles::add);
    // 高亮
    getHighlight(rPr).ifPresent(labelStyles::add);
    return labelStyles;
  }


}
